#pragma once

#include <opencv2\opencv.hpp>

#include "fiocpp.hpp"
#include "fiowin.hpp"
#include <thread>



class CompressPage
{
public:

	static void CompressPNG(std::string path, int flag = CV_LOAD_IMAGE_UNCHANGED)
	{
		cv::Mat m = cv::imread(path, flag);
		if (m.empty()) return;
		m = m.colRange(1, m.cols);
		std::vector<int> compression_params;
		compression_params.push_back(CV_IMWRITE_PNG_COMPRESSION);
		compression_params.push_back(3);
		cv::imwrite(path.substr(0, path.size() - 4) + ".png", m, compression_params);
	}

	static void Compress(std::string folder, size_t nThread = 4, int flag = CV_LOAD_IMAGE_UNCHANGED)
	{
		std::vector<std::string> fnl, fol;
		GetFolderContentList(fnl, fol, folder);

		std::vector<std::thread> ths;

		for (size_t i = 0; i < fnl.size(); i++)
		{
			std::cout << i << "/" << fnl.size() << "\n";

			ths.push_back(std::thread(CompressPNG, (folder + fnl[i]), flag));

			if (ths.size() >= nThread)
			{
				ths[0].join();
				ths.erase(ths.begin());
			}
		}

		for (auto &th : ths)
			th.join();
	}
	

	static cv::Mat GetHistogram(const cv::Mat &m)
	{
		/// Establish the number of bins
		int histSize = 256;

		/// Set the ranges ( for B,G,R) )
		float range[] = { 0, 256 };
		const float* histRange = { range };

		bool uniform = true;
		bool accumulate = false;

		cv::Mat hist;

		/// Compute the histograms:
		cv::calcHist(&m, 1, 0, cv::Mat(), hist, 1, &histSize, &histRange, uniform, accumulate);

		return hist;

		cv::Mat dp(hist.size(), hist.type());
		for (size_t i = 0; i < dp.total(); i++)dp.at<float>(i) = i;
		cv::hconcat(dp, hist, dp);

		std::cout << dp << "\n";

		return dp;
	}

	static cv::Mat DrawHistogram(const cv::Mat &hist, cv::Scalar color, int hist_w = 512, int hist_h = 400)
	{
		// Draw the histograms for B, G and R
		int bin_w = cvRound((double)hist_w / hist.total());

		cv::Mat histImage(hist_h, hist_w, CV_8UC3, cv::Scalar(0, 0, 0));

		/// Normalize the result to [ 0, histImage.rows ]
		cv::normalize(hist, hist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat());

		/// Draw for each channel
		for (int i = 1; i < hist.total(); i++)
		{
			line(histImage, cv::Point(bin_w*(i - 1), hist_h - cvRound(hist.at<float>(i - 1))),
				cv::Point(bin_w*(i), hist_h - cvRound(hist.at<float>(i))),
				color, 2, 8, 0);
		}
		return histImage;
	}

	static cv::Mat GetHistogram(const cv::Mat &m, cv::Scalar color, int hist_w = 512, int hist_h = 400)
	{
		return DrawHistogram(GetHistogram(m), color, hist_w, hist_h);
	}

	static void histo(std::string path)
	{
		cv::Mat m = cv::imread(path, CV_LOAD_IMAGE_UNCHANGED);
		cv::cvtColor(m, m, CV_BGR2HSV);
		std::vector<cv::Mat> ml;

		cv::split(m, ml);

		cv::Scalar color[3] = {
			cv::Scalar(255, 0, 0),
			cv::Scalar(0, 255, 0),
			cv::Scalar(0, 0, 255)
		};

		for (size_t i = 0; i < ml.size(); i++)
		{
			char buf[16];
			sprintf(buf, "ch %u", i);
			/// Display
			cv::namedWindow(buf, cv::WINDOW_AUTOSIZE);
			cv::imshow(buf, GetHistogram(ml[i], color[i % 3]));
		}

		cv::waitKey(0);
	}
	
	static cv::Mat ToBlackAndWhite(const cv::Mat &m)
	{
		//cv::blur(m, m, cv::Size(3, 3));

		cv::Mat bw;
		double thres = cv::threshold(m, bw, 255, 255, cv::THRESH_OTSU | cv::THRESH_BINARY);

		cv::Mat hist = GetHistogram(m);

		std::cout << "hist=" << hist.t() << "\n";

		int black = 0;
		int white = 255;

		int i = 0;
		for (; i < thres; i++)
		{
			if (hist.at<float>(i) > hist.at<float>(black))
				black = i;
		}

		for (; i < hist.rows; i++)
		{
			if (hist.at<float>(i) > hist.at<float>(white))
				white = i;
		}

		std::cout << "thres=" << thres << "\tblack=" << black << "\twhite=" << white << "\n";

		for (size_t ind = 0; ind < bw.total(); ind++)
		{
			if (bw.at<uchar>(ind)>0)
				bw.at<uchar>(ind) = white;
			else
				bw.at<uchar>(ind) = black;
		}

		return bw;
	}

	static void CompressBlackAndWhite(std::string path, int compress = 9)
	{
		cv::Mat m = cv::imread(path, CV_LOAD_IMAGE_UNCHANGED);
		if (m.empty() || m.channels()!=1) return;
		std::vector<int> compression_params;
		compression_params.push_back(CV_IMWRITE_PNG_COMPRESSION);
		compression_params.push_back(compress);
		cv::Mat bw = ToBlackAndWhite(m);
		cv::imwrite(path.substr(0, path.size() - 4) + "_bw.png", bw, compression_params);
	}

	

	static void CompressBW(std::string folder, size_t nThread = 4, int compress = 9)
	{
		std::vector<std::string> fnl, fol;
		GetFolderContentList(fnl, fol, folder);

		std::vector<std::thread> ths;

		for (size_t i = 0; i < fnl.size(); i++)
		{
			std::cout << i << "/" << fnl.size() << "\n";

			ths.push_back(std::thread(CompressBlackAndWhite, folder + fnl[i], compress));

			if (ths.size() >= nThread)
			{
				ths[0].join();
				ths.erase(ths.begin());
			}
		}

		for (auto &th : ths)
			th.join();
	}


	static void Compress3a(std::string path)
	{
		cv::Mat m = cv::imread(path, CV_LOAD_IMAGE_UNCHANGED);
		if (m.empty()) return;
		std::vector<int> compression_params;
		compression_params.push_back(CV_IMWRITE_PNG_COMPRESSION);
		compression_params.push_back(9);

		std::vector<cv::Mat> ml;
		cv::split(m, ml);
		std::vector<cv::Mat> bw(ml.size());

		for (size_t i = 0; i < ml.size(); i++)
		{
			bw[i] = ToBlackAndWhite(ml[i]);
		}

		cv::merge(bw, m);

		path.insert(path.end() - 4, '_');
		path.insert(path.end() - 4, 'b');
		path.insert(path.end() - 4, 'w');
		cv::imwrite(path, m, compression_params);
	}

	static void Compress4(std::string folder, uchar black = 90, uchar white = 230)
	{
		int i = 5;
		char buf[16];
		sprintf(buf, "IMG_%03d", i);
		CompressBlackAndWhite(folder + std::string(buf) + ".png");
		i++;
		while (i < 180)
		{
			sprintf(buf, "IMG_%03d_%03d", i, i + 1);
			CompressBlackAndWhite(folder + std::string(buf) + ".png");
			i += 2;
		}
		sprintf(buf, "IMG_%03d", i);
		CompressBlackAndWhite(folder + std::string(buf) + ".png");
	}

	
	int compress;
	int nThread;

	CompressPage()
		: compress(9)
		, nThread(1)
	{

	}

	void Load(const CvFileStorage* fs)
	{
		compress = cvReadIntByName(fs, 0, "compress", compress);

		nThread = cvReadIntByName(fs, 0, "nThread", nThread);
	}

	bool Load(std::string configFile)
	{
		CvFileStorage* fs = cvOpenFileStorage(configFile.c_str(), 0, CV_STORAGE_READ);
		if (fs)
		{
			Load(fs);
			cvReleaseFileStorage(&fs);
			return true;
		}
		std::cout << "fail load " << configFile << "\n";
		return false;
	}


	void Save(CvFileStorage* fs)
	{
		cvWriteInt(fs, "compress", compress);

		cvWriteInt(fs, "nThread", nThread);
	}

	bool Save(std::string configFile)
	{
		CvFileStorage* fs = cvOpenFileStorage(configFile.c_str(), 0, CV_STORAGE_WRITE);
		if (fs)
		{
			Save(fs);
			cvReleaseFileStorage(&fs);
			return true;
		}
		std::cout << "fail save " << configFile << "\n";
		return false;
	}


	void RunCom(std::string path, std::string paramPath)
	{
		Load(paramPath);
		CompressBW(path, nThread, compress);
	}


};
